#ifndef __case_epoll_commutication_kenny_kfjiweur89u83r33e3e__
#define __case_epoll_commutication_kenny_kfjiweur89u83r33e3e__

#include <iostream>
#include <set>
#include <atomic>
#include <thread>

#include <hude/base/log.hpp>
#include <hude/base/hex.hpp>
#include <hude/aio/epoll.hpp>
#include <hude/utils/serialport.hpp>

#include "test/test.hpp"

namespace test
{
    using namespace hude;
    using namespace hude::utils;

    //--------------------------------------------------------------------------------
    struct case_epoll_t : public hude::test::case_t
    {
        enum : size_t { emCOUNT = 500U, emINTERVAL = 2U };

        using sport_t    = hude::utils::serialport_t;
        using epoll_t    = hude::aio::epoll_plus_t< sport_t*, 1U >;
        using select_t    = hude::aio::select_plus_t< sport_t*, 1U >;

        std::atomic< bool >    running_;
        sport_t                sport_;
        epoll_t                epoll_;
        select_t               select_;

        case_epoll_t() :
            running_( true ), epoll_( false )
        {
        }

        bool run() override
        {
            // 接收数据
            auto cb_sport = [=]( uint32_t event, sport_t* sport )
            {
                hude::buffer_t buffer( 1024 );

                auto capacity = buffer.size();

                if( event & epoll_t::evt_t::emEPOLLIN )
                {
                    size_t count = 0;

                    while( count < capacity )
                    {
                        auto ret = sport->read( (char*)&(buffer[count]), capacity - count );

                        if( ret > 0 )
                        {
                            count += ret;
                        } else {
                            break;
                        }
                    }

                    buffer.resize( count );

                    log_dug( "input data: %d(%s)", count, to_hex( buffer, count ).c_str() );
                } else {
                    log_dug( "not input event: %#x", event );
                }
            };

            // 发送数据
            auto cb_th = [this]()
            {
                std::this_thread::sleep_for( std::chrono::milliseconds( 1U ) );

                buffer_t buffer{ 0xff, 0, 0, 0, 0, 0, 0, 0 };

                uint32_t* index = (uint32_t*)( &(buffer[4]) );

                for( auto i = 0U; i < emCOUNT; ++i )
                {
                    log_dug( "write sport: %u(%s), times(%d)", buffer.size(), to_hex( buffer ).c_str(), i );

                    *index = i;

                    sport_.write( &(buffer[0]), buffer.size() );

                    std::this_thread::sleep_for( std::chrono::milliseconds( emINTERVAL ) );
                }

                std::this_thread::sleep_for( std::chrono::milliseconds( 200U ) );

                running_ = false;
            };

            /////////////////////////////////////////////////////
            // 初始化串口
            auto tty = get_tty();

            if( ! sport_.open( tty ) ||
                ! sport_.settings( 115200, 0U ) )
            {
                log_err( "open serial port: %s", tty );

                return false;
            } else {
                static const char* p = "abc";

                hude::buffer_t buffer{ p, p + 4 };

                sport_.write( &(buffer[0]), 4U );

                std::this_thread::sleep_for( std::chrono::milliseconds( 10U ) );

                auto ret = sport_.read( &(buffer[0]), 4U );

                if( ret != 4 ) return false;
            }

            log_inf( "== select part ==========================" );
            test( select_, cb_sport, cb_th );

            std::this_thread::sleep_for( std::chrono::milliseconds( 2000U ) );

            log_inf( "== epoll part ==========================" );
            test( epoll_, cb_sport, cb_th );

            return true;
        }

        //--------------------------------------------------------------------------------
        // 测试过程
        template< typename _poll >
        bool test( _poll& poll, std::function< void( uint32_t event, sport_t* sport ) > cb_recv, std::function< void() > cb_send )
        {
            // 初始化poll
            if( ! poll.create() )
            {
                log_err( "create poll." );
                return false;
            }

            // 添加sport到poll监听
            poll.add( sport_.handle(), cb_recv, &sport_ );

            auto th_send = std::thread( cb_send );

            // 启动poll事件监听
            while( running_ )
            {
                poll.wait( 100U );
            }

            // 退出清理
            th_send.join();

            poll.del( sport_.handle() );

            poll.close();

            return true;
        };

        //--------------------------------------------------------------------------------
        // 列出系统所有串口设备
        static size_t all_serial( std::set< std::string >& serials )
        {
            auto pp = popen("ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d '/' -f 5", "r");
            if (!pp) return 0;

            char tmp[1024];

            std::ostringstream oss;

            while (fgets(tmp, sizeof(tmp), pp) != NULL)
                oss << tmp;

            pclose(pp);

            std::istringstream iss( oss.str() );

            std::set< std::string > ss{
                std::istream_iterator< std::string >( iss ),
                std::istream_iterator< std::string >()
            };

            serials.swap( ss );

            return serials.size();
        }

        //--------------------------------------------------------------------------------
        // 如果没有目标串口需要手动输入
        static const char* get_tty( bool def = true )
        {
            const char* tty = nullptr;

            std::set< std::string > ss;
            if( 0 == all_serial( ss ) ) return nullptr;

            if( def )
            {
                static const char* serial_board   = "/dev/ttymxc3";
                static const char* serial_x64     = "/dev/ttyACM0";

                if( ss.find( serial_board  + 5 ) != ss.end() ) { tty = serial_board ; log_dug( "board  tty: %s", tty ); }
                if( ss.find( serial_x64    + 5 ) != ss.end() ) { tty = serial_x64   ; log_dug( "x64    tty: %s", tty ); }

                if( tty != nullptr ) return tty;
            }

            {
                std::cout << "current exist serial ports:" << std::endl;

                for( const auto& sp : ss )
                {
                    std::cout << sp << std::endl;
                }

                std::string name;

                while( name.empty() )
                {
                    std::cout << "input use serial port: ";
                    std::cin >> name;
                    std::cout << std::endl;

                    if( ss.find( name ) == ss.end() )
                    {
                        char y;

                        std::cout << "Are you use the port: %s, (y/n) ";
                        std::cin >> y;
                        std::cout << std::endl;

                        if( y == 'y' ) break;

                        name.clear();
                    }
                }

                static char ret[64];

                auto dev = std::string( "/dev/" ) + name;
                strncpy( ret, dev.c_str(), dev.length() );

                return ret;
            }
        }
    };

    //--------------------------------------------------------------------------------
    TEST_CASE_ADD( "epoll", case_epoll_t, false );

} // namespace test

#endif // __case_epoll_commutication_kenny_kfjiweur89u83r33e3e__



